package main

import (
	"go/ast"
	"go/token"
	"go/types"
	"os"

	"gitee.com/FlyingOnion/wtflog"
	. "gitee.com/FlyingOnion/wtflog/types"
	"golang.org/x/tools/go/packages"
)

var (
	logger = wtflog.NewTextLogger()
)

type spec struct {
	has     bool
	idents  []identSpec
	structs []structSpec
	maps    []mapSpec
	arrays  []arraySpec
}

type arraySpec struct {
	name      string
	arrayType *ast.ArrayType
}

type identSpec struct {
	name  string
	ident *ast.Ident
}

type structSpec struct {
	name       string
	structType *ast.StructType
}

type mapSpec struct {
	keyType          string
	isValueBasicType bool
	name             string
	mapType          *ast.MapType
}

func getPkg(path string) (*packages.Package, error) {
	cfg := &packages.Config{
		Mode:  packages.LoadSyntax,
		Dir:   path,
		Tests: false,
	}
	pkgs, err := packages.Load(cfg)
	if err != nil {
		return nil, err
	}
	if len(pkgs) > 0 {
		return pkgs[0], nil
	}
	return nil, nil
}

func getSpecs(pkg *packages.Package) *spec {
	structs := []structSpec{}
	maps := []mapSpec{}
	idents := []identSpec{}
	arrays := []arraySpec{}
	has := false
	// fmt.Println(pkg.TypesInfo.Types)
	for _, f := range pkg.Syntax {
		for _, decl := range f.Decls {
			if gdecl, ok := decl.(*ast.GenDecl); ok && gdecl.Tok == token.TYPE {
				for _, spec := range gdecl.Specs {
					if typeSpec, ok2 := spec.(*ast.TypeSpec); ok2 {
						if typeSpec.Name.Name == "_" {
							continue
						}
						has = true
						switch t := typeSpec.Type.(type) {
						case *ast.StructType:
							structs = append(structs, structSpec{
								name:       typeSpec.Name.Name,
								structType: t,
							})
						case *ast.Ident:
							if tv, ok := pkg.TypesInfo.Types[t]; ok {
								if types.IsInterface(tv.Type) {
									logger.Warn(typeSpec.Name.Name, "is an interface, skipped.")
								} else {
									idents = append(idents, identSpec{
										name:  typeSpec.Name.Name,
										ident: t,
									})
								}
							}
						case *ast.ArrayType:
							arrays = append(arrays, arraySpec{
								name:      typeSpec.Name.Name,
								arrayType: t,
							})
						case *ast.MapType:
							// 一些自定义的类型，底层是int, string基本类型的，也需要支持
							if keyTypeValue, ok := pkg.TypesInfo.Types[t.Key]; ok {
								keyType := keyTypeValue.Type.Underlying().String()
								switch keyType {
								case "string", "byte",
									"int", "int8", "int16", "int32", "int64",
									"uint", "uint8", "uint16", "uint32", "uint64":
									isValueBasicType := false
									vt := types.ExprString(t.Value)
									switch vt {
									case "string", "byte",
										"int", "int8", "int16", "int32", "int64",
										"uint", "uint8", "uint16", "uint32", "uint64":
										isValueBasicType = true
									}
									maps = append(maps, mapSpec{
										keyType:          keyType,
										isValueBasicType: isValueBasicType,
										name:             typeSpec.Name.Name,
										mapType:          t,
									})
								default:
									logger.Warn(typeSpec.Name.Name, "is a map with unsupported key type.")
									logger.Warn("Supported key types (underlying): string, byte, and int/uint family.")
								}
							}

						}
					}
				}
			}
		}
	}
	return &spec{
		has:     has,
		structs: structs,
		idents:  idents,
		arrays:  arrays,
		maps:    maps,
	}
}

func main() {
	paths := os.Args[1:]
	if len(paths) == 0 {
		paths = []string{"."}
	}
	b := NewByteSlice(make([]byte, 0, 200))
	showBuiltinFieldWarning := true
	for _, path := range paths {
		pkg, err := getPkg(path)
		if err != nil {
			logger.Error(err)
			os.Exit(1)
		}
		if pkg == nil {
			logger.Info("No packages found in", path)
			continue
		}
		ss := getSpecs(pkg)
		if !ss.has {
			logger.Info("No custom types in", path)
			continue
		}
		logger.Info("Got",
			Int(len(ss.structs)), "type struct(s),",
			Int(len(ss.arrays)), "type array(s),",
			Int(len(ss.idents)), "type ident(s),",
			Int(len(ss.maps)), "type map(s) in",
			path,
		)

		b.Reset()
		b.AppendString("// Code generated by gen-bsa; DO NOT EDIT.\n\n").
			AppendString("package ").AppendString(pkg.Name).AppendString("\n\n"). // TODO: modify package name
			AppendString("import \"fmt\"\n").
			AppendString("import \"sort\"\n").
			AppendString("import \"unsafe\"\n").
			AppendString("import . \"gitee.com/FlyingOnion/wtflog/types\"\n\n"). // TODO: modify import pkg
			AppendString("func fmtPrint(vs ...interface{}) string { return fmt.Sprintf(\"%+v\", vs...) }\n\n").
			AppendString("func fmtBool(b bool) string { if b { return \"true\" }; return \"false\"}\n\n").
			AppendString("func sortKeys(keys interface{}, less func(i, j int) bool) { sort.Slice(keys, less) }\n").
			AppendString("var _ unsafe.Pointer\n\n")

		// generate AppendTo for custom structures
		for _, _struct := range ss.structs {
			if len(_struct.structType.Fields.List) == 0 {
				b.AppendString("func (").AppendString(_struct.name).AppendString(") AppendTo(b *ByteSlice) { b.AppendString(\"{}\") }\n\n")
				continue
			}
			b.AppendString("func (p ").AppendString(_struct.name).AppendString(") AppendTo(b *ByteSlice) {\n").
				AppendString("\tvar any interface{}\n").
				AppendString("\t_ = any\n").
				AppendString("\tb.AppendString(\"{")
			hasLoggedField := false
			for _, structField := range _struct.structType.Fields.List {
				if len(structField.Names) == 0 {
					if hasLoggedField {
						// b.AppendString("\tb.AppendByte(' ').AppendString(\"")
						b.AppendString("\tb.AppendString(\" ")
					} else {
						hasLoggedField = true
					}
					// a 0-name field means a builtin type
					if showBuiltinFieldWarning {
						logger.Warn("Builtin struct field is not fully supported. The generated codes may have syntax errors, or the log will be different from your expectation. We suggest you set the name of the field explicitly.")
						showBuiltinFieldWarning = false
					}
					if t, ok := structField.Type.(*ast.Ident); ok {
						name := "p." + t.Name
						b.AppendString(t.Name).AppendString(":\")\n")
						switch t.Name {
						case "bool":
							appendBool(b, name, 1)
						case "byte":
							appendByte(b, name, 1)
						case "string":
							appendString(b, name, 1)
						case "error":
							appendError(b, name, 1)
						case "int", "int8", "int16", "int32", "int64":
							appendInt(b, name, 1)
						case "uint", "uint8", "uint16", "uint32", "uint64":
							appendUint(b, name, 1)
						default:
							appendAny(b, name, 1)
						}
						continue
					}
					if t, ok := structField.Type.(*ast.StarExpr); ok {
						switch x := t.X.(type) {
						case *ast.Ident:
							name := "p." + x.Name
							b.AppendString(x.Name).AppendString(":\")\n")
							b.AppendString("\tif ").AppendString(name).AppendString(" == nil {\n\t\tb.AppendString(\"<nil>\")\n\t} else {\n")
							switch x.Name {
							case "bool":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendBool, name, 2)
								// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
								// 	AppendString("\t\t\tAppendByte('(').AppendString(fmtBool(*elem)).AppendByte(')')\n")
							case "byte":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendByte, name, 2)
								// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
								// 	AppendString("\t\t\tAppendByte('(').AppendByte(*elem)).AppendByte(')')\n")
							case "string":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendString, name, 2)
							case "int", "int8", "int16", "int32", "int64":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendInt, name, 2)
							case "uint", "uint8", "uint16", "uint32", "uint64":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendUint, name, 2)
							default:
								appendAny(b, name, 2)
							}
							b.AppendString("\t}\n")
						case *ast.SelectorExpr:
							name := "p." + x.Sel.Name
							b.AppendString("\tif ").AppendString(name).AppendString(" == nil {\n\t\tb.AppendString(\"<nil>\")\n\t} else {\n")
							appendAny(b, name, 2)
							b.AppendString("\t}\n")
						}
						continue
					}
					continue
				}
				// for j, fieldName := range structField.Names {
				// }
				// if structField.Names[0].Name != "_" {
				// 	if i > 0 {
				// 		b.AppendString("\tb.AppendByte(' ').AppendString(\"")
				// 	}
				// } else {
				// 	continue
				// }
				for _, fieldName := range structField.Names {
					if fieldName.Name == "_" {
						continue
					}
					if hasLoggedField {
						// b.AppendString("\tb.AppendByte(' ').AppendString(\"")
						b.AppendString("\tb.AppendString(\" ")
					} else {
						hasLoggedField = true
					}
					b.AppendString(fieldName.Name).AppendString(":\")\n") // add field name
					name := "p." + fieldName.Name
					switch t := structField.Type.(type) {
					case *ast.Ident:
						switch t.Name {
						case "bool":
							appendBool(b, name, 1)
						case "byte":
							appendByte(b, name, 1)
						case "rune":
							appendRune(b, name, 1)
						case "string":
							appendString(b, name, 1)
						case "error":
							appendError(b, name, 1)
						case "int", "int8", "int16", "int32", "int64":
							appendInt(b, name, 1)
						case "uint", "uint8", "uint16", "uint32", "uint64":
							appendUint(b, name, 1)
						default:
							appendAny(b, name, 1)
						}
					case *ast.ArrayType:
						b.AppendString("\tb.AppendByte('[')\n").
							AppendString("\tfor i, elem := range p.").AppendString(fieldName.Name).AppendString(" {\n").
							AppendString("\t\tif i > 0 { b.AppendByte(' ') }\n")
						switch et := t.Elt.(type) {
						case *ast.Ident:
							switch et.Name {
							// case "byte":
							// 	appendUint(b, "elem", 2)
							// b.AppendString("\t\tb.AppendUint64(uint64(elem))\n")
							case "bool":
								appendBool(b, "elem", 2)
								// b.AppendString("\t\tb.AppendString(fmtBool(elem))\n")
							case "string":
								appendString(b, "elem", 2)
								// b.AppendString("\t\tb.AppendString(elem)\n")
							case "error":
								appendError(b, "elem", 2)
								// b.AppendString("\t\tb.AppendString(elem.Error())\n")
							case "int", "int8", "int16", "int32", "int64":
								appendInt(b, "elem", 2)
								// b.AppendString("\t\tb.AppendInt64(int64(elem))\n")
							case "byte", "uint", "uint8", "uint16", "uint32", "uint64":
								appendUint(b, "elem", 2)
								// b.AppendString("\t\tb.AppendUint64(uint64(elem))\n")
							default:
								appendAny(b, "elem", 2)
								// b.AppendString("\t\tany = elem\n").
								// 	AppendString("\t\tif bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }\n")
							}
							b.AppendString("\t}\n\tb.AppendByte(']')\n")
							if et.Name == "byte" {
								b.AppendString("\tif len(p.").AppendString(fieldName.Name).AppendString(") > 0 { b.AppendByte('(').AppendBytes(p.").AppendString(fieldName.Name).AppendString(").AppendByte(')') }\n")
							}
						case *ast.StarExpr:
							b.AppendString("\t\tif elem == nil { b.AppendString(\"<nil>\"); continue }\n")
							switch x := et.X.(type) {
							case *ast.Ident:
								switch x.Name {
								case "bool":
									appendPointer(b, "elem", 2)
									appendPointerElem(b, appendBool, "elem", 2)
									// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
									// 	AppendString("\t\t\tAppendByte('(').AppendString(fmtBool(*elem)).AppendByte(')')\n")
								case "byte":
									appendPointer(b, "elem", 2)
									appendPointerElem(b, appendByte, "elem", 2)
									// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
									// 	AppendString("\t\t\tAppendByte('(').AppendByte(*elem)).AppendByte(')')\n")
								case "string":
									appendPointer(b, "elem", 2)
									appendPointerElem(b, appendString, "elem", 2)
								case "int", "int8", "int16", "int32", "int64":
									appendPointer(b, "elem", 2)
									appendPointerElem(b, appendInt, "elem", 2)
								case "uint", "uint8", "uint16", "uint32", "uint64":
									appendPointer(b, "elem", 2)
									appendPointerElem(b, appendUint, "elem", 2)
								// switch x.Name {
								// case "bool", "byte", "string",
								// 	"int", "int8", "int16", "int32", "int64",
								// 	"uint", "uint8", "uint16", "uint32", "uint64":
								// 	// TODO: modify each type pointer
								// 	// b.AppendString("\tfor i, elem := range p.").AppendString(fieldName.Name).AppendString(" {\n").
								// 	// 	AppendString("\t\tif i > 0 { b.AppendString(\" \") }\n").
								// 	// b.AppendString("\t\tif elem == nil { b.AppendString(\"<nil>\"); continue }\n").
								// 	b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).AppendByte('(')\n")
								// 	// AppendString("\t}\n")
								// 	// AppendString("\tb.AppendByte(']')\n")
								default:
									// b.AppendString("\tfor i, elem := range p.").AppendString(fieldName.Name).AppendString(" {\n").
									// 	AppendString("\t\tif i > 0 { b.AppendString(\" \") }\n").
									// b.AppendString("\t\tany = elem\n").
									// 	AppendString("\t\tif bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }\n").
									appendAny(b, "elem", 2)
									// b.AppendString("\t}\n")
									// AppendString("\tb.AppendByte(']')\n")
								}
							default:
								appendAny(b, "elem", 2)
								// b.AppendString("\tfor i, elem := range p.").AppendString(fieldName.Name).AppendString(" {\n").
								// 	AppendString("\t\tif i > 0 { b.AppendString(\" \") }\n").
								// 	AppendString("\t\tany = elem\n").
								// 	AppendString("\t\tif bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }\n").
								// 	AppendString("\t}\n")
							}
							b.AppendString("\t}\n\tb.AppendByte(']')\n")
						default:
							appendAny(b, "elem", 2)
							b.AppendString("\t}\n\tb.AppendByte(']')\n")
						}
						// b.AppendString("\t}\n")
					case *ast.SelectorExpr:
						appendAny(b, name, 1)
					case *ast.StarExpr:
						switch x := t.X.(type) {
						case *ast.Ident:
							b.AppendString("\tif ").AppendString(name).AppendString(" == nil {\n\t\tb.AppendString(\"<nil>\")\n\t} else {\n")
							switch x.Name {
							case "bool":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendBool, name, 2)
								// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
								// 	AppendString("\t\t\tAppendByte('(').AppendString(fmtBool(*elem)).AppendByte(')')\n")
							case "byte":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendByte, name, 2)
								// b.AppendString("\t\tb.AppendPointer(uintptr(unsafe.Pointer(elem))).\n").
								// 	AppendString("\t\t\tAppendByte('(').AppendByte(*elem)).AppendByte(')')\n")
							case "string":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendString, name, 2)
							case "int", "int8", "int16", "int32", "int64":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendInt, name, 2)
							case "uint", "uint8", "uint16", "uint32", "uint64":
								appendPointer(b, name, 2)
								appendPointerElem(b, appendUint, name, 2)
							default:
								appendAny(b, name, 2)
							}
							b.AppendString("\t}\n")
						case *ast.SelectorExpr:
							b.AppendString("\tif ").AppendString(name).AppendString(" == nil {\n\t\tb.AppendString(\"<nil>\")\n\t} else {\n")
							appendAny(b, name, 2)
							b.AppendString("\t}\n")
						}
					case *ast.FuncType, *ast.ChanType:
						appendPointer(b, "&p."+fieldName.Name, 1)
					}
				}
			}
			if !hasLoggedField {
				b.AppendString("}\")\n}\n\n")
				continue
			}
			b.AppendString("\tb.AppendByte('}')\n}\n\n")
		}

		// generate AppendTo for custom idents
		for _, _ident := range ss.idents {
			b.AppendString("func (p ").AppendString(_ident.name).AppendString(") AppendTo(b *ByteSlice) {\n")
			switch _ident.ident.Name {
			case "bool":
				appendBool(b, "bool(p)", 1)
			// error cannot be initialized
			// case "error":
			// 	appendError(b, "error(p)", 1)
			case "string":
				appendString(b, "string(p)", 1)
			case "rune", "int", "int8", "int16", "int32", "int64":
				appendInt(b, "p", 1)
			case "byte", "uint", "uint8", "uint16", "uint32", "uint64":
				appendUint(b, "p", 1)
			default:
				b.AppendString("\tvar any interface{}\n").
					AppendString("\t_ = any\n")
				appendAny(b, _ident.ident.Name+"(p)", 1)
			}
			b.AppendString("}\n\n")
		}

		for _, _array := range ss.arrays {
			b.AppendString("func (p ").AppendString(_array.name).AppendString(") AppendTo(b *ByteSlice) {\n").
				AppendString("\tif p == nil { b.AppendString(\"<nil>\"); return }\n").
				AppendString("\tvar any interface{}\n").
				AppendString("\t_ = any\n").
				AppendString("\tb.AppendByte('[')\n").
				AppendString("\tfor i, elem := range p {\n").
				AppendString("\t\tif i > 0 { b.AppendByte(' ') }\n")
			switch et := _array.arrayType.Elt.(type) {
			case *ast.Ident:
				switch et.Name {
				case "bool":
					appendBool(b, "elem", 2)
				case "string":
					appendString(b, "elem", 2)
				case "error":
					appendError(b, "elem", 2)
				case "int", "int8", "int16", "int32", "int64":
					appendInt(b, "elem", 2)
				case "byte", "uint", "uint8", "uint16", "uint32", "uint64":
					appendUint(b, "elem", 2)
				default:
					appendAny(b, "elem", 2)
				}
			default:
				appendAny(b, "elem", 2)
			}
			b.AppendString("\t}\n").
				AppendString("\tb.AppendByte(']')\n").
				AppendString("}\n\n")
		}

		for _, _map := range ss.maps {
			vType := "interface{}"
			if _map.isValueBasicType {
				vType = types.ExprString(_map.mapType.Value)
			}
			kvStruct := NewByteSlice(make([]byte, 0, 80))
			kvStruct.AppendString("struct{k ").AppendString(_map.keyType).AppendString("; v ").AppendString(vType).AppendByte('}')
			b.AppendString("func (p ").AppendString(_map.name).AppendString(") AppendTo(b *ByteSlice) {\n").
				AppendString("\tif p == nil { b.AppendString(\"<nil>\"); return }\n").
				AppendString("\tif len(p) == 0 { b.AppendString(\"{}\"); return }\n").
				AppendString("\tvar any interface{}\n").
				AppendString("\t_ = any\n").
				AppendString("\tb.AppendByte('{')\n").
				AppendString("\tkvs := make([]").Append(kvStruct).AppendString(", 0, len(p))\n").
				AppendString("\tfor k, v := range p { kvs = append(kvs, ").Append(kvStruct).AppendString("{k: ").AppendString(_map.keyType).AppendString("(k), v: ").AppendString(vType).AppendString("(v)}) }\n").
				AppendString("\tsortKeys(kvs, func(i, j int) bool { return kvs[i].k < kvs[j].k })\n").
				AppendString("\tfor i, kv := range kvs {\n").
				AppendString("\t\tif i > 0 { b.AppendByte(' ') }\n")
			switch _map.keyType {
			case "byte":
				appendByte(b, "kv.k", 2)
			case "string":
				appendString(b, "kv.k", 2)
			case "int", "int8", "int16", "int32", "int64":
				appendInt(b, "kv.k", 2)
			case "uint", "uint8", "uint16", "uint32", "uint64":
				appendUint(b, "kv.k", 2)
			default:
				b.AppendString("\t\tb.AppendString(fmtPrint(kv.k))\n")
			}
			b.AppendString("\t\tb.AppendByte(':')\n")

			switch vType {
			case "bool":
				appendBool(b, "kv.v", 2)
			case "byte":
				appendByte(b, "kv.v", 2)
			case "string":
				appendString(b, "kv.v", 2)
			case "error":
				appendError(b, "kv.v", 2)
			case "int", "int8", "int16", "int32", "int64":
				appendInt(b, "kv.v", 2)
			case "uint", "uint8", "uint16", "uint32", "uint64":
				appendUint(b, "kv.v", 2)
			case "interface{}":
				fallthrough
			default:
				appendAny(b, "kv.v", 2)
			}

			b.AppendString("\t}\n").
				AppendString("\tb.AppendByte('}')\n").
				AppendString("}\n\n")
		}

		filepath := path + "/wtf__.go"
		logger.Info("Write to file", filepath)
		f, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
		if err != nil {
			logger.Error(err)
			panic(err)
		}
		f.Write(b.Bytes())
		f.Close()
	}
	logger.Info("Done.")
}

const indent = "\t" // a tab

// type typeAppender struct {
// 	basic, pointer func(b *ByteSlice, name string, level int)
// }

func appendByte(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendByte(").AppendString(name).AppendString(")\n")
}

func appendRune(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendRune(").AppendString(name).AppendString(")\n")
}

func appendInt(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendInt64(int64(").AppendString(name).AppendString("))\n")
}

func appendUint(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendUint64(uint64(").AppendString(name).AppendString("))\n")
}

func appendBool(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendString(fmtBool(").AppendString(name).AppendString("))\n")
}

func appendString(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendString(").AppendString(name).AppendString(")\n")
}

func appendAny(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("any = ").AppendString(name).AppendByte('\n')
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("if bsa, ok := any.(ByteSliceAppender); ok { bsa.AppendTo(b) } else if str, ok := any.(Stringer); ok { b.AppendString(str.String()) } else { b.AppendString(fmtPrint(any)) }\n")
}

func appendError(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("if ").AppendString(name).AppendString(" == nil { b.AppendString(\"<nil>\") } else { b.AppendString(\"error(\").AppendString(").AppendString(name).AppendString(".Error()).AppendString(\")\") }\n")
}

func appendPointer(b *ByteSlice, name string, level int) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendPointer(uintptr(unsafe.Pointer(").AppendString(name).AppendString(")))\n")
}

func appendPointerElem(b *ByteSlice,
	appendElem func(*ByteSlice, string, int),
	ptrName string,
	level int,
) {
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendByte('(')\n")
	appendElem(b, "*"+ptrName, level)
	for i := 0; i < level; i++ {
		b.AppendString(indent)
	}
	b.AppendString("b.AppendByte(')')\n")
}
